﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Collections;

namespace FlightToolkit.SimProp
{
    internal class OrderedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
    {
        internal OrderedDictionary()
        {
            this.keyToIndex = new Dictionary<TKey, int>();
            this.values = new List<TValue>();
        }

        internal void Add(TKey key, TValue value)
        {
            keyToIndex.Add(key, values.Count);
            values.Add(value);
        }

        internal bool ContainsKey(TKey key)
        {
            return this.keyToIndex.ContainsKey(key);
        }

        internal int IndexOfKey(TKey key)
        {
            return this.keyToIndex[key];
        }

        internal TKey GetKeyByIndex(int index)
        {
            return this.keyToIndex.First(x => x.Value == index).Key;
        }

        internal TValue GetValueByIndex(int index)
        {
            return this.values[index];
        }

        internal int Count { get { return this.values.Count; } }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            for (int i = 0; i < values.Count; ++i)
            {
                yield return new KeyValuePair<TKey, TValue>(GetKeyByIndex(i), GetValueByIndex(i));
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private Dictionary<TKey, int> keyToIndex;
        private List<TValue> values;
    }

    internal static class SPB
    {
        #region Internal
        internal static bool IsSPBFormat(Stream inputStream)
        {
            Format version;
            bool isSPB = ReadSPBHeader(inputStream, out version);
            inputStream.Seek(0, SeekOrigin.Begin);

            return isSPB;
        }

        internal static PropertySet Read(Symbols symbols, Stream inputStream)
        {
            var idToObjectSizeTable = new OrderedDictionary<Guid, int>();

            BinaryReader br = new BinaryReader(inputStream);

            Format format;
            if (!ReadSPBHeader(inputStream, out format))
            {
                throw new InvalidDataException("This is not an SPB file");
            }


            int guidCount = br.ReadInt32();

            for (int i = 0; i < guidCount; ++i)
            {
                idToObjectSizeTable.Add(new Guid(br.ReadBytes(16)), br.ReadInt32());
            }

            // Lookup guid
            int guidIndex = br.ReadInt32();
            SymbolDef sym = symbols.GetSymbolDef(idToObjectSizeTable.GetKeyByIndex(guidIndex));

            // First symbol should be the document.
            if (sym.Name != "Document")
            {
                throw new Exception("Unexpected first symbol.");
            }

            var documentSet = new PropertySet((PropertySetDef)sym);
            documentSet.ReadBinaryData(symbols, idToObjectSizeTable, format, br);
            return documentSet;
        }

        internal static void Write(PropertySet documentSet, Stream outputStream, Format format)
        {
            var idToObjectSizeTable = new OrderedDictionary<Guid, int>();

            BinaryWriter bw = new BinaryWriter(outputStream);

            bw.Write(MAGIC_HEADER_VALUE);
            bw.Write((uint)(format == Format.FlightBinary ? 20 : 16));
            bw.Write((uint)format);
            bw.Write((uint)0);
            bw.Write((uint)(format == Format.FlightBinary ? 0x200 : 0x300));
            if (format == Format.FlightBinary)
            {
                bw.Write((uint)0x04E4);
            }

            idToObjectSizeTable.Add(Guid.Empty, 0);
            documentSet.BuildGuidTable(idToObjectSizeTable);

            bw.Write((Int32)idToObjectSizeTable.Count);
            foreach (var entry in idToObjectSizeTable)
            {
                bw.Write(entry.Key.ToByteArray());
                bw.Write((Int32)entry.Value);
            }

            documentSet.WriteBinaryData(idToObjectSizeTable, format, bw);

            bw.Flush();
        }
        #endregion

        #region Private
        private const ushort MAGIC_HEADER_VALUE = 0xEBAC;


        private static bool ReadSPBHeader(Stream inputStream, out Format format)
        {
            format = Format.FlightBinary;

            BinaryReader br = new BinaryReader(inputStream);

            ushort magicNumber = br.ReadUInt16();
            if (magicNumber != MAGIC_HEADER_VALUE)
            {
                // Invalid magic header value
                return false;
            }

            int headerSize = br.ReadInt32();
            if (headerSize < 16 || headerSize > 20)
            {
                // Unexpected header size
                return false;
            }

            int versionMajor = br.ReadInt32();
            int versionMinor = br.ReadInt32();
            if ((versionMajor != (int)Format.FSXBinary && versionMajor != (int)Format.FlightBinary) ||
                versionMinor != 0)
            {
                // Unexpected version
                return false;
            }
            format = (Format)versionMajor;

            int unknown = br.ReadInt32();
            if (format == Format.FlightBinary)
            {
                int unknown2 = br.ReadInt32();
            }

            return true;
        }
        #endregion
    }
}
